#include <stdlib.h>
#include <stdio.h>
#include <string.h>

unsigned int read_int(FILE *fp)
{
	unsigned char buffer[4];
	if (4 != fread(buffer,1,4,fp))
	{
		printf("Error: failed to read 4 bytes from file\n");
		exit(-1);
	}
	return ( buffer[0] |
		(buffer[1] << 8) |
		(buffer[2] << 16) |
		(buffer[3] << 24) );
}

void write_int(const unsigned int value, FILE *fp)
{
	unsigned char buffer[4];
	buffer[0] = value & 0x000000FF;
	buffer[1] = (value & 0x0000FF00) >> 8;
	buffer[2] = (value & 0x00FF0000) >> 16;
	buffer[3] = (value & 0xFF000000) >> 24;
	if (4 != fwrite(buffer,1,4,fp))
	{
		printf("Error: failed to write 4 bytes to file\n");
		exit(-1);
	}
}

void write_short(const unsigned short value, FILE *fp)
{
	unsigned char buffer[2];
	buffer[0] = value & 0x00FF;
	buffer[1] = (value & 0xFF00) >> 8;
	if (2 != fwrite(buffer,1,2,fp))
	{
		printf("Error: failed to write 2 bytes to file\n");
		exit(-1);
	}
}

void write_char(const unsigned char value, FILE *fp)
{
	if (1 != fwrite(&value,1,1,fp))
	{
		printf("Error: failed to write 1 byte to file\n");
		exit(-1);
	}
}

unsigned int mul4(unsigned int base)
{
	if (base & 3)
	{
		return (base & 0xFFFFFFFC) + 4;
	}
	return base;
}

void dump_bmp(const char *output_fname, const unsigned char *palette, const unsigned int w, const unsigned int h, const unsigned char *image_data)
{
	unsigned int i;
	unsigned int pad_w = 0;

	if (w & 3)
	{
		pad_w = ((w & 0xFFFFFFFC) + 4) - w;
	}

	FILE *fp = fopen(output_fname,"wb");
	if (!fp)
	{
		printf("Error: could not open %s\n",output_fname);
		perror("Error reported was");
		exit(-1);
	}

	fwrite("BM",1,2,fp);	// BMP header
	write_int(14 + 40 + 1024 + ((w + pad_w) * h), fp);	// file size
	write_short(0,fp);	// reserved
	write_short(0,fp);	// reserved
	write_int(14 + 40 + 1024, fp);	// offset of image data
	write_int(40, fp);	// BITMAPCOREHEADER size
	write_int(w, fp);	// (true) width
	write_int(h, fp);	// height
	write_short(1,fp);	// color planes
	write_short(8,fp);	// bits-per-pixel
	write_int(0, fp);	// compression type
	write_int((w + pad_w) * h, fp);	// size of image data
	write_int(0, fp);	// horiz. resolution (pixels-per-meter)
	write_int(0, fp);	// vres (ppm)
	write_int(256, fp);	// palette size
	write_int(256, fp);	// "important" colors in palette

	// write palette
	for (i = 0; i < 256; i ++)
	{
		write_char(palette[3 * i + 2],fp);
		write_char(palette[3 * i + 1],fp);
		write_char(palette[3 * i],fp);
		write_char(0,fp);
	}

	// write image buffer
	for (i = 0; i < h; i ++)
	{
		unsigned char zero[3] = {0};
		fwrite(&image_data[i * w],1,w,fp);
		fwrite(zero,1,pad_w,fp);
	}

	// all done, close file
	fclose(fp);
}


int main (int argc, char *argv[])
{
	FILE *fp = NULL;

	unsigned char palette[768];

	unsigned int *x, *y, *tag;
	unsigned int i,j;

	if (argc != 4)
	{
		printf("extract_bmp\nGreg Kennedy 2012\npart of the MisCopter project\nhttp://code.google.com/p/miscopter\n\nUsage: %s <palette> <packed-bmp> <output-prefix>\n",argv[0]);
		exit(-1);
	}

	// Step 1: open file, read palette
	fp = fopen(argv[1],"rb");
	if (!fp)
	{
		printf("Error: could not open %s\n",argv[1]);
		perror("Error reported was");
		exit(-1);
	}
	if (768 != fread(palette,1,768,fp))
	{
		printf("Error: failed to read 768 bytes from %s\n",argv[1]);
		exit(-1);
	}
	fclose(fp);

	// Step 2: open packed-bmp file
	fp = fopen(argv[2],"rb");
	if (!fp)
	{
		printf("Error: could not open %s\n",argv[2]);
		perror("Error reported was");
		exit(-1);
	}
	fseek(fp,0,SEEK_END);
	int calc_filesize = ftell(fp);
	rewind(fp);

	unsigned int filesize = read_int(fp);
	printf("Calculated filesize %d, reported filesize %d\n",calc_filesize,filesize);
	unsigned int dummy = read_int(fp);
	printf("\tDummy field, expected value 0, got %d\n",dummy);
	unsigned int num_images = read_int(fp);
	printf("** %d textures in file **\n",num_images);
	unsigned int num_meta = read_int(fp);
	printf("** %d meta-info entries file **\n",num_meta);
	x = malloc(num_meta * sizeof(int));
	y = malloc(num_meta * sizeof(int));
	tag = malloc(num_meta * sizeof(int));

	for (i = 0; i < num_meta; i ++)
	{
		char string_tag[5] = {'\0'};

		x[i] = read_int(fp);
		y[i] = read_int(fp);
		tag[i] = read_int(fp);
		memcpy(string_tag,&tag[i],4);
		printf("\tMeta entry %d: x=%d y=%d tag=%04x (%s)\n",i,x[i],y[i],tag[i],string_tag);
	}

	for (i = 0; i < num_images; i ++)
	{
		printf("** IMAGE %d **\n",i);
		unsigned int local_x = read_int(fp);
		printf("Meta width %d, local width %d\n",x[i],local_x);
		unsigned int local_y = read_int(fp);
		printf("Meta width %d, local width %d\n",y[i],local_y);
		dummy = read_int(fp);
		printf("\tDummy field, expected value 0, got %d\n",dummy);

		int offset = 0;
		for (j = 0; j < local_y; j ++)
		{
			unsigned int offset_absolute = read_int(fp);
			printf("\tRow %d: offset %d, calculated %d\n",j,offset,offset_absolute);
			offset += local_x;
		}

		// image data begins here
		unsigned char *image_data = malloc(local_x * local_y);
		if (local_x * local_y != fread(image_data, 1, local_x * local_y, fp))
		{
			printf("Error: failed to read %d bytes from file\n",local_x * local_y);
			exit(-1);
		}

		char output_fname[256];
		sprintf(output_fname,"%s%04d.bmp",argv[3],i);
		dump_bmp(output_fname,palette,local_x,local_y,image_data);

		printf("** Wrote image %s **\n",output_fname);
	}

	fclose(fp);

	return 0;
}
